Skip to content

feat: enforce expected allowed tools#105

Merged
mertsatilmaz merged 2 commits into
OWASP:mainfrom
snoopuppy582:feat/89-allowed-tools-assertion
May 16, 2026
Merged

feat: enforce expected allowed tools#105
mertsatilmaz merged 2 commits into
OWASP:mainfrom
snoopuppy582:feat/89-allowed-tools-assertion

Conversation

@snoopuppy582

Copy link
Copy Markdown
Contributor

Fixes #89

Summary

Extends no_denied_tool_call so expected.allowed_tools is enforced as an allowlist when present.

Changes

  • Keeps expected.denied_tools as the existing denylist semantics.
  • Treats expected.allowed_tools as an allowlist; an empty list means no tool calls are allowed.
  • Reports unexpected tool names and the configured allowlist in failure evidence.
  • Validates expected.allowed_tools and expected.denied_tools shapes during scenario loading.
  • Adds unit tests, a trace fixture demonstrating an unexpected tool call, and docs/README updates.

Tests

  • uv run pytest tests/test_assertions.py tests/test_scenarios.py -q -> 34 passed
  • uv run pytest tests/test_cli.py tests/test_live_http.py tests/test_assertions.py tests/test_scenarios.py -q -> 51 passed
  • uv run pytest -q -> 143 passed
  • uv run --with ruff ruff check src tests -> passed
  • git diff --check -> passed

AI-assisted contribution disclosure

  • Tool used: OpenAI Codex.
  • AI-assisted parts: implementation draft, tests, docs wording, and PR preparation.
  • Human review: I reviewed the final diff, kept the assertion behavior scoped to no_denied_tool_call, verified the allowlist/denylist precedence, and ran the tests/checks listed above.

@mertsatilmaz mertsatilmaz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for implementing allowed_tools enforcement.

Blocking issue: allowlist enforcement can be bypassed by a tool call that lacks a recognized name field. extract_observed_tool_names silently drops any tool_call without name/tool/tool_name, so with expected.allowed_tools: [] a trace containing an unnamed tool call can still pass.

Please treat unnamed tool calls as policy violations when expected.allowed_tools is configured, or otherwise surface them as an assertion error/failure. Add tests for allowed_tools: [] with an unnamed tool call and for allowed_tools with a malformed/nameless tool call.

Small docs cleanup: the README example lists summarize_document as an allowed tool, but that reads like a goal id rather than a tool name. Please use an actual tool name or adjust the example.

@snoopuppy582

snoopuppy582 commented May 13, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review. I addressed the allowlist bypass in the follow-up commit.

Changes:

  • expected.allowed_tools now fails when a tool call lacks a recognized name, tool, or tool_name field.
  • Added tests for allowed_tools: [] with an unnamed tool call and for a malformed/nameless tool call with a non-empty allowlist.
  • Updated the README and scenario spec examples to use read_document as an actual allowed tool name instead of a goal id.

Verified locally:

  • python -m pytest tests/test_assertions.py -q -> 28 passed

GitHub Actions test is also passing on the PR.

@mertsatilmaz mertsatilmaz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this addresses the previous requested changes. I rechecked the unnamed/malformed tool-call path and the README cleanup, and the local test suite passes. This looks ready to merge.

@mertsatilmaz mertsatilmaz merged commit e4de273 into OWASP:main May 16, 2026
1 check passed
mertsatilmaz added a commit to Tech-Psycho95/Agent-Security-Regression-Harness that referenced this pull request May 17, 2026
…ntions

- Restore no_denied_tool_call test coverage from main that was lost when
  this branch merged main: 7 tests covering allowed_tools allowlist
  behavior (added by PR OWASP#105).
- Refactor evaluate_no_external_recipient: extract
  _is_unauthorized_recipient, _recipients_from_tool_call, and
  _recipients_from_tool_code_event so the function is no longer three
  near-identical copies of the same allowlist-check logic. Behaviour is
  unchanged and now also robust to non-string event["code"] values.
- Add the scenario.schema.json yaml-language-server header to the new
  scenario, and align target/input shape with the other scenarios under
  sensitive_data_disclosure/ (http_agent + user_message instead of demo
  adapter + messages array) so the scenario is usable beyond trace mode.

Co-authored-by: Tech-Psycho95 <shivamsingh.smn@gmail.com>
Co-authored-by: mertsatilmaz <mert.satilmaz@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement expected.allowed_tools enforcement

2 participants